Micron Document
🎖️GitЯра🎖️

Commit 1dd47bc09032fe0972af7eed6a7554e61be7a02b


Parents : f4b6b02
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-05-18T07:52:17-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-05-18T12:52:17Z

fix(settings): add input validation for BLE PIN, LoRa modem, and ambient lighting (#5477)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Changes
Diff

diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/AmbientLightingConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/AmbientLightingConfigItemList.kt
index 3099e9e8ad..a751e5fc80 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/AmbientLightingConfigItemList.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/AmbientLightingConfigItemList.kt
@@ -21,6 +21,7 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.platform.LocalFocusManager
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
@@ -38,6 +39,9 @@ import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.proto.ModuleConfig
+private const val MAX_LED_CURRENT = 31
+private const val MAX_RGB_VALUE = 255
+
@Composable
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
@@ -67,36 +71,72 @@ fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> U
containerColor = CardDefaults.cardColors().containerColor,
)
HorizontalDivider()
- EditTextPreference(
- title = stringResource(Res.string.current),
- value = formState.value.current,
- enabled = state.connected,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(current = it) },
- )
- EditTextPreference(
- title = stringResource(Res.string.red),
- value = formState.value.red,
- enabled = state.connected,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(red = it) },
- )
- EditTextPreference(
- title = stringResource(Res.string.green),
- value = formState.value.green,
- enabled = state.connected,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(green = it) },
- )
-
- EditTextPreference(
- title = stringResource(Res.string.blue),
- value = formState.value.blue,
+ LedColorFields(
+ config = formState.value,
enabled = state.connected,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(blue = it) },
+ focusManager = focusManager,
+ onConfigChange = { formState.value = it },
)
}
}
}
}
+
+@Composable
+private fun LedColorFields(
+ config: ModuleConfig.AmbientLightingConfig,
+ enabled: Boolean,
+ focusManager: FocusManager,
+ onConfigChange: (ModuleConfig.AmbientLightingConfig) -> Unit,
+) {
+ androidx.compose.foundation.layout.Column {
+ EditTextPreference(
+ title = stringResource(Res.string.current),
+ value = config.current,
+ enabled = enabled,
+ isError = config.current !in 0..MAX_LED_CURRENT,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in 0..MAX_LED_CURRENT) {
+ onConfigChange(config.copy(current = it))
+ }
+ },
+ )
+ EditTextPreference(
+ title = stringResource(Res.string.red),
+ value = config.red,
+ enabled = enabled,
+ isError = config.red !in 0..MAX_RGB_VALUE,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in 0..MAX_RGB_VALUE) {
+ onConfigChange(config.copy(red = it))
+ }
+ },
+ )
+ EditTextPreference(
+ title = stringResource(Res.string.green),
+ value = config.green,
+ enabled = enabled,
+ isError = config.green !in 0..MAX_RGB_VALUE,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in 0..MAX_RGB_VALUE) {
+ onConfigChange(config.copy(green = it))
+ }
+ },
+ )
+ EditTextPreference(
+ title = stringResource(Res.string.blue),
+ value = config.blue,
+ enabled = enabled,
+ isError = config.blue !in 0..MAX_RGB_VALUE,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in 0..MAX_RGB_VALUE) {
+ onConfigChange(config.copy(blue = it))
+ }
+ },
+ )
+ }
+}

diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/BluetoothConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/BluetoothConfigItemList.kt
index 08d27bbefb..fd83155f68 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/BluetoothConfigItemList.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/BluetoothConfigItemList.kt
@@ -17,11 +17,17 @@
package org.meshtastic.feature.settings.radio.component
import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.resources.Res
@@ -37,6 +43,8 @@ import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.proto.Config
+private const val PIN_LENGTH = 6
+
@Composable
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
@@ -79,18 +87,41 @@ fun BluetoothConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
onItemSelected = { formState.value = formState.value.copy(mode = it) },
)
HorizontalDivider()
- EditTextPreference(
- title = stringResource(Res.string.fixed_pin),
- value = formState.value.fixed_pin,
+ FixedPinPreference(
+ pinValue = formState.value.fixed_pin,
enabled = state.connected,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = {
- if (it.toString().length == 6) { // ensure 6 digits
- formState.value = formState.value.copy(fixed_pin = it)
- }
- },
+ focusManager = focusManager,
+ onPinChange = { formState.value = formState.value.copy(fixed_pin = it) },
)
}
}
}
}
+
+@Composable
+private fun FixedPinPreference(
+ pinValue: Int,
+ enabled: Boolean,
+ focusManager: androidx.compose.ui.focus.FocusManager,
+ onPinChange: (Int) -> Unit,
+) {
+ var pinState by remember(pinValue) { mutableStateOf(pinValue.toString().padStart(PIN_LENGTH, '0')) }
+ val pinIsError = pinState.length != PIN_LENGTH || !pinState.all { it.isDigit() }
+ EditTextPreference(
+ title = stringResource(Res.string.fixed_pin),
+ value = pinState,
+ enabled = enabled,
+ isError = pinIsError,
+ keyboardOptions =
+ KeyboardOptions.Default.copy(keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done),
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = { input ->
+ if (input.length <= PIN_LENGTH && input.all { it.isDigit() }) {
+ pinState = input
+ if (input.length == PIN_LENGTH) {
+ onPinChange(input.toInt())
+ }
+ }
+ },
+ )
+}

diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
index f0dab11e1c..ced63fff66 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/component/LoRaConfigItemList.kt
@@ -64,6 +64,9 @@ import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.feature.settings.util.hopLimits
import org.meshtastic.proto.Config
+private val SPREAD_FACTOR_RANGE = 7..12
+private val CODING_RATE_RANGE = 5..8
+
@Composable
fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
@@ -115,28 +118,11 @@ fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
onItemSelected = { formState.value = formState.value.copy(modem_preset = it) },
)
} else {
- EditTextPreference(
- title = stringResource(Res.string.bandwidth),
- value = formState.value.bandwidth,
- enabled = state.connected && !formState.value.use_preset,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(bandwidth = it) },
- )
- HorizontalDivider()
- EditTextPreference(
- title = stringResource(Res.string.spread_factor),
- value = formState.value.spread_factor,
- enabled = state.connected && !formState.value.use_preset,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(spread_factor = it) },
- )
- HorizontalDivider()
- EditTextPreference(
- title = stringResource(Res.string.coding_rate),
- value = formState.value.coding_rate,
- enabled = state.connected && !formState.value.use_preset,
- keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
- onValueChanged = { formState.value = formState.value.copy(coding_rate = it) },
+ ManualModemSettings(
+ config = formState.value,
+ enabled = state.connected,
+ focusManager = focusManager,
+ onConfigChange = { formState.value = it },
)
}
}
@@ -250,3 +236,47 @@ fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
}
}
}
+
+@Composable
+private fun ManualModemSettings(
+ config: Config.LoRaConfig,
+ enabled: Boolean,
+ focusManager: androidx.compose.ui.focus.FocusManager,
+ onConfigChange: (Config.LoRaConfig) -> Unit,
+) {
+ androidx.compose.foundation.layout.Column {
+ EditTextPreference(
+ title = stringResource(Res.string.bandwidth),
+ value = config.bandwidth,
+ enabled = enabled,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = { onConfigChange(config.copy(bandwidth = it)) },
+ )
+ HorizontalDivider()
+ EditTextPreference(
+ title = stringResource(Res.string.spread_factor),
+ value = config.spread_factor,
+ enabled = enabled,
+ isError = config.spread_factor !in SPREAD_FACTOR_RANGE,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in SPREAD_FACTOR_RANGE) {
+ onConfigChange(config.copy(spread_factor = it))
+ }
+ },
+ )
+ HorizontalDivider()
+ EditTextPreference(
+ title = stringResource(Res.string.coding_rate),
+ value = config.coding_rate,
+ enabled = enabled,
+ isError = config.coding_rate !in CODING_RATE_RANGE,
+ keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+ onValueChanged = {
+ if (it in CODING_RATE_RANGE) {
+ onConfigChange(config.copy(coding_rate = it))
+ }
+ },
+ )
+ }
+}

Served by rngit 1.5.4 - Generated in 0.05s